/*-
 * Convert.java          Contains PDU conversion methods
 *
 * $LastChangedBy: stein $
 * $LastChangedRevision: 37 $
 * $LastChangedDate: 2006-11-30 23:06:29 +0000 (Thu, 30 Nov 2006) $
 * $HeadURL: svn://localhost/SMS/src/com/googlecode/attention/sms/pdu/UDHI.java $
 *
 * Copyright (c) 2007, Stein Gunnar Bakkeby
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Stein Gunnar Bakkeby nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.googlecode.attention.util;

/**
 * This class contains PDU conversion methods.
 *
 * @author Stein Gunnar Bakkeby
 */
public class Convert {

  /**
   * This list contains the hexadecimal strings from integer value 0 - 255.
   * The list has been implemented to make conversion faster than using
   * Integer.toHexString(int), this conversion also always ensures that the
   * returned value is always two characters, while the numbers 10-15 are
   * represented with the capital letters A-F.
   */
  public static final String[] hex = new String[] {
    "00","01","02","03","04","05","06","07","08","09","0A","0B","0C","0D","0E","0F",
    "10","11","12","13","14","15","16","17","18","19","1A","1B","1C","1D","1E","1F",
    "20","21","22","23","24","25","26","27","28","29","2A","2B","2C","2D","2E","2F",
    "30","31","32","33","34","35","36","37","38","39","3A","3B","3C","3D","3E","3F",
    "40","41","42","43","44","45","46","47","48","49","4A","4B","4C","4D","4E","4F",
    "50","51","52","53","54","55","56","57","58","59","5A","5B","5C","5D","5E","5F",
    "60","61","62","63","64","65","66","67","68","69","6A","6B","6C","6D","6E","6F",
    "70","71","72","73","74","75","76","77","78","79","7A","7B","7C","7D","7E","7F",
    "80","81","82","83","84","85","86","87","88","89","8A","8B","8C","8D","8E","8F",
    "90","91","92","93","94","95","96","97","98","99","9A","9B","9C","9D","9E","9F",
    "A0","A1","A2","A3","A4","A5","A6","A7","A8","A9","AA","AB","AC","AD","AE","AF",
    "B0","B1","B2","B3","B4","B5","B6","B7","B8","B9","BA","BB","BC","BD","BE","BF",
    "C0","C1","C2","C3","C4","C5","C6","C7","C8","C9","CA","CB","CC","CD","CE","CF",
    "D0","D1","D2","D3","D4","D5","D6","D7","D8","D9","DA","DB","DC","DD","DE","DF",
    "E0","E1","E2","E3","E4","E5","E6","E7","E8","E9","EA","EB","EC","ED","EE","EF",
    "F0","F1","F2","F3","F4","F5","F6","F7","F8","F9","FA","FB","FC","FD","FE","FF"
  };
  
  /**
   * This list contains the hexadecimal strings from integer value 0 - 15.
   * The list has been implemented to make conversion faster than using
   * Integer.toHexString(int), this conversion also always ensures that the
   * returned value is always in upper case.
   */
  public static final String[] hex_single = new String[] {
    "0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"
  };
  
  /**
   * This list have been implemented to make conversion faster than using
   * Integer.toString(int) for time (e.g. range 0 to 59). The additional
   * numbers up to 99 have been included due to other possible uses. This list
   * also makes it easier to maintain the zero in front of the first ten
   * numbers.
   */
  public static final String[] integers = new String[] {
    "00","01","02","03","04","05","06","07","08","09",
    "10","11","12","13","14","15","16","17","18","19",
    "20","21","22","23","24","25","26","27","28","29",
    "30","31","32","33","34","35","36","37","38","39",
    "40","41","42","43","44","45","46","47","48","49",
    "50","51","52","53","54","55","56","57","58","59",
    "60","61","62","63","64","65","66","67","68","69",
    "70","71","72","73","74","75","76","77","78","79",
    "80","81","82","83","84","85","86","87","88","89",
    "90","91","92","93","94","95","96","97","98","99"
  };
  
  /**
   * Adds missing 0's to a binary string.
   *
   * @param binaryString The bit String to add zeros to.
   * @param totalBits The total number of bits that should be in the String.
   * @return The String with added zeros.
   */
  public static String addZeros(final String binaryString,
                                final int totalBits) {
    final int count = totalBits - binaryString.length();
    if (count > 0) {
      switch (count) {
      case 1: return "0" + binaryString;
      case 2: return "00" + binaryString;
      case 3: return "000" + binaryString;
      case 4: return "0000" + binaryString;
      case 5: return "00000" + binaryString;
      case 6: return "000000" + binaryString;
      case 7: return "0000000" + binaryString;
      default:
        final StringBuilder zero = new StringBuilder(totalBits);
        final int length = totalBits - binaryString.length();
        /* Adds missing 0's */
        for(int i = 0; i < length; i++) zero.append("0");
        zero.append(binaryString);
        return zero.toString();
      }
    }
    return binaryString;
  }
  
  /**
   * Method for converting a binary string into hex. Note that
   * this method does not add any missing zeros.
   * 
   * @param bin The binary string
   * @return The hexadecimal value
   */
  public static String binToHex(final char bin) {
    return intToHex(Integer.parseInt("" + bin, 2));
  }
  
  /**
   * Method for converting a binary string into hex.
   * 
   * @param bin The binary string
   * @param totalBits Total number of chars in this hex number
   * @return The hexadecimal value
   */
  public static String binToHex(final char bin, final int totalBits) {
    return intToHex(Integer.parseInt("" + bin, 2), totalBits);
  }
  
  /**
   * Method for converting a binary string into hex. Note that
   * this method does not add any missing zeros.
   * 
   * @param bin The binary string
   * @return The hexadecimal value
   */
  public static String binToHex(final String bin) {
    return intToHex(Integer.parseInt(bin, 2));
  }
  
  /**
   * Method for converting a binary string into hex.
   * 
   * @param bin The binary string
   * @param totalBits Total number of chars in this hex number 
   * @return The hexadecimal value
   */
  public static String binToHex(final String bin, final int totalBits) {
    return intToHex(Integer.parseInt(bin, 2), totalBits);
  }
  
  /**
   * Method for converting a binary string into integer.
   * 
   * @param bin The binary string
   * @return The integer
   */
  public static int binToInt(final char bin) {
    return Integer.parseInt("" + bin, 2);
  }

  /**
   * Method for converting a int array into integer.
   * 
   * @param bin The int array
   * @return The integer
   */
  public static int binToInt(final int[] bin) {
    int res = 0;
    int length = bin.length - 1;
    for (int i = 0; i <= length; i++) {
      if (bin[length - i] == 1) {
        res += Math.pow(2, i);
      }
    }
    return res;
  }
  
  /**
   * Method for converting a IntArrayBuilder string into integer.
   * 
   * @param bin The IntArrayBuilder string
   * @return The integer
   */
  public static int binToInt(final IntArrayBuilder bin) {
    return binToInt(bin.toArray());
  }
  
  /**
   * Method for converting a binary string into integer.
   * 
   * @param bin The binary string
   * @return The integer
   */
  public static int binToInt(final String bin) {
    return Integer.parseInt(bin, 2);
  }
  
  /**
   * This will convert a binary String into an int array.
   * 
   * @param data The binary string
   * @return An int array
   */
  public static int[] binToIntArray(final String data) {
    char[] chars = data.toCharArray();
    int length = chars.length;
    int[] ints = new int[length];
    for (int i = 0; i < length; i++) {
      ints[i] = (chars[i] == '1' ? 1 : 0);
    }
    return ints;
  }

  /**
   * This will fuse each pair of 4bit values into 8bit values. This method
   * expects an even number of values as fusing an odd number of values would
   * be inherently wrong. If an odd number of values is supplied, the last
   * value will be lost.
   * 
   * @param data The data to fuse
   * @return The fused data
   */
  public static int[] fuse4bitInto8bit(final int[] data) {
    final int length = data.length;
    final int newLength = length / 2;
    final int[] output = new int[newLength];
    int index = 0;
    for (int i = 0; i < newLength; i += 2) {
      output[index++] = (data[i] << 4) | data[i + 1];
    }
    return output;
  }
  
  /**
   * This will fuse each pair of 4bit values into 8bit values. This method
   * expects an even number of values as fusing an odd number of values would
   * be inherently wrong. If an odd number of values is supplied, the last
   * value will be lost.
   * 
   * @param data The data to fuse
   * @return The fused data
   */
  @CopiesReference
  public static IntArrayBuilder fuse4bitInto8bit(final IntArrayBuilder data) {
    final int length = data.length();
    final IntArrayBuilder output = new IntArrayBuilder(length);
    for (int i = 0; i < length; i += 2) {
      output.append((data.intAt(i) << 4) | data.intAt(i + 1));
    }
    return output;
  }

  /**
   * Converts an octet in hex into a binary string. Note that this method
   * does not add any missing zeros.
   *
   * @param hex The hexadecimal value
   * @return The binary string of this value
   */
  public static String hexToBin(final String hex) {
    return intToBin(Integer.parseInt(hex, 16));
  }
  
  /**
   * Converts an octet in hex into a binary string with a defined
   * number of bits.
   *
   * @param hex The hexadecimal value
   * @param totalBits The total number of bits that should be in the String
   * @return The binary string of this value
   */
  public static String hexToBin(final String hex, final int totalBits) {
    return intToBin(Integer.parseInt(hex, 16), totalBits);
  }
  
  /**
   * Converts an octet in hex into integer.
   *
   * @param hex The hexadecimal value
   * @return The integer value interpreted by the hex value
   */
  public static int hexToInt(final String hex) {
    return Integer.parseInt(hex, 16);
  }
  
  /* Static methods */
  /**
   * This will convert a hex string into an array of integers.
   * 
   * @param data The hex string
   * @return An array of integers where data expected to be 2 chars = 1 int
   */
  public static int[] hexToIntArray(final String data) {
    return hexToIntArray(data, 2);
  }
  
  /**
   * This will convert a hex string into an array of integers.
   * 
   * @param data The hex string
   * @param charsPerInt Number of characters to use when converting to int
   * @return An array of integers where data expected to be charsPerInt = 1 int
   */
  public static int[] hexToIntArray(final String data, final int charsPerInt) {
    int len = data.length();
    double length = data.length() / charsPerInt;
    boolean rest = (length != (int)length ? true : false);
    int size = (int) length;
    int[] array = new int[rest ? size + 1 : size];
    int index = 0;
    for (int i = 0; i < len; i += charsPerInt) {
      array[index++] = Integer.parseInt(data.substring(i, i + charsPerInt),16);
    }
    if (rest) array[index] = Integer.parseInt(
        data.substring(size * charsPerInt), 16);
    return array;
  }
  
  /**
   * Converts a value in integer into a binary string. Note that this
   * method does not add any missing zeros.
   *
   * @param integer The integer
   * @return The binary string of this integer
   */
  public static String intToBin(final int integer) {
    return Integer.toBinaryString(integer);
  }
  
  /**
   * Converts a value in integer into a binary string with a defined
   * number of bits.
   * 
   * @param integer The integer
   * @param totalBits The total number of bits that should be in the String
   * @return The binary string of this integer
   */
  public static String intToBin(final int integer, final int totalBits) {
    return addZeros(Integer.toBinaryString(integer), totalBits);
  }
  
  /**
   * Converts an array of ints into an array of chars.
   * 
   * @param data The int array
   * @return The char array
   */
  public static char[] intToChar(final int[] data) {
    final int length = data.length;
    final char[] output = new char[length];
    for (int i = 0; i < length; i++) {
      output[i] = (char)data[i];
    }
    return output;
  }
  
  /**
   * Converts an array of integers into an array of chars.
   * 
   * @param data The int array
   * @return The char array
   */
  public static char[] intToChar(final IntArrayBuilder data) {
    final int length = data.length();
    final char[] output = new char[length];
    for (int i = 0; i < length; i++) {
      output[i] = (char)data.intAt(i);
    }
    return output;
  }
  
  /**
   * Converts an octet in integer to hex. Note that this method
   * returns a string of two characters, for example "0B".
   *
   * @param integer The integer to be converted into hex (one octet).
   * @return The hexadecimal interpretation of the integer.
   * @see #intToHex(int, int)
   */
  public static String intToHex(final int integer) {
    return hex[integer & 0xFF];
  }
  
  /* Int, hex, and bin conversion methods. */
  /**
   * Converts an octet in integer to hex.
   *
   * @param integer The integer to be converted into hex (one octet).
   * @param charsPerInt Total number of chars in this hex number
   * @return The hexadecimal interpretation of the integer.
   */
  public static String intToHex(final int integer, final int charsPerInt) {
    final String output;
    switch (charsPerInt) {
    case 1:
      output = Integer.toHexString(integer);
      break;
      
    case 2:
      output = hex[integer & 0xFF];
      break;
      
    default:
      output = addZeros(hex[integer & 0xFF], charsPerInt);
    }
    return output;
  }
  
  /**
   * This will convert an array of integers into a hex string. Note that this
   * method by default converts each integer into two hex characters (up to
   * 0xFF). If the array needs to be converted into single hex characters (up
   * to 0xF), then use the method intToHex(int[], int) instead.
   * 
   * @param array The array of integers
   * @return The hex string
   * @see #intToHex(int[], int)
   */
  public static String intToHex(final int[] array) {
    final int length = array.length;
    final StringBuilder sb = new StringBuilder(length * 2);
    for (int i = 0; i < length; i++) {
      sb.append(hex[array[i] & 0xFF]);
    }
    return sb.toString();
  }
  
  /**
   * This will convert an array of integers into a hex string.
   * 
   * @param array The array of integers
   * @param charsPerInt Number of characters to produce per integer
   * @return The hex string
   */
  public static String intToHex(final int[] array, final int charsPerInt) {
    final int length = array.length;
    final StringBuilder sb = new StringBuilder(length * charsPerInt);
    switch (charsPerInt) {
    case 1:
      for (int i = 0; i < length; i++) {
        sb.append(hex_single[array[i]]);
      }
      break;
    
    case 2:
      for (int i = 0; i < length; i++) {
        sb.append(hex[array[i] & 0xFF]);
      }
      break;
      
    default:
      for (int i = 0; i < length; i++) {
        sb.append(addZeros(hex[array[i] & 0xFF], charsPerInt));
      }
      break;
    }
    return sb.toString();
  }
  
  /**
   * This will convert an array of integers into a hex string. Note that this
   * method by default converts each integer into two hex characters (up to
   * 0xFF). If the array needs to be converted into single hex characters (up
   * to 0xF), then use the method intToHex(IntArrayBuilder, int) instead.
   * 
   * @param array The array of integers
   * @return The hex string
   * @see #intToHex(IntArrayBuilder, int)
   */
  public static String intToHex(final IntArrayBuilder array) {
    final int length = array.length();
    final StringBuilder sb = new StringBuilder(length * 2);
    for (int i = 0; i < length; i++) {
      // TODO would be good to do a performance test here
      // to see whether array.intAt(i) is faster or slower than
      // array.toArray() then var[i]. Mind that var[i] is essentially
      // also a (final) method call under the hood... I recon array.intAt(i)
      // will be faster in this instance as the arrays tend to be relatively
      // short.
      sb.append(hex[array.intAt(i) & 0xFF]);
    }
    return sb.toString();
  }
  
  /**
   * This will convert an array of integers into a hex string.
   * 
   * @param array The array of integers
   * @param charsPerInt Number of characters to produce per integer
   * @return The hex string
   */
  public static String intToHex(final IntArrayBuilder array,
                                final int charsPerInt) {
    final int length = array.length();
    final StringBuilder sb = new StringBuilder(length * charsPerInt);
    switch (charsPerInt) {
    case 1:
      for (int i = 0; i < length; i++) {
        sb.append(Integer.toHexString(array.intAt(i)));
      }
      break;
    
    case 2:
      for (int i = 0; i < length; i++) {
        sb.append(hex[array.intAt(i) & 0xFF]);
      }
      break;
      
    default:
      for (int i = 0; i < length; i++) {
        sb.append(addZeros(hex[array.intAt(i) & 0xFF], charsPerInt));
      }
      break;
    }
    return sb.toString();
  }
  
  /**
   * This will convert an integer into a binary int array.
   * 
   * @param data The integer
   * @param totalBits The total number of bits
   * @return An int array
   */
  public static int[] intToIntArray(final int data, final int totalBits) {
    final char[] bin = intToBin(data, totalBits).toCharArray();
    final int length = bin.length;
    final int[] output = new int[length];
    for (int i = 0; i < length; i++) {
      output[i] = (bin[i] == '1' ? 1 : 0);
    }
    return output;
  }
  
  /**
   * Converts integers in the range 0 to 99 into a two character string. This
   * means that integers in the range 0 to 9 will be returned "00", "01", etc.
   * If a value less than 0 or greater than 99, then an
   * IllegalArgumentException is thrown.
   * 
   * @param value Integer in the range 0-99
   * @return String version of the integer
   * @throws IllegalArgumentException If integer out of bounds
   */
  public static String intToString(final int value) {
    if (value > integers.length || value < 0) {
      throw new IllegalArgumentException(
          "value > " + integers.length + " || value < 0 (" + value + ")");
    }
    return integers[value];
  }
  
  /**
   * This will convert an int array into a String.
   * 
   * @param data Int array
   * @return int[] --> char[] --> String
   */
  public static String intToString(final int[] data) {
    return new String(intToChar(data));
  }
  
  /**
   * This will convert an int array into a String.
   * 
   * @param data Int array
   * @return int[] --> char[] --> String
   */
  public static String intToString(final IntArrayBuilder data) {
    return new String(intToChar(data));
  }
  
  /**
   * Swaps every pair of ints in an int array.
   *
   * @param octets The array to swap octets
   * @return The array where the octets are swapped
   */
  public static int[] nibble(final int[] octets) {
    final int[] output = new int[octets.length];
    final int length = octets.length;
    for (int i = 0; i < length; i++) {
      output[i] = ((octets[i] & 0xF0) >> 4) + ((octets[i] & 0xF) << 4);
    }
    return output;
  }
  /* End character set static methods. */
  
  /**
   * Swaps every pair of chars in a String.
   *
   * @param octets The String to swap octets, the length has to be even.
   * @return The String where the octets are swapped.
   */
  public static String nibble(final String octets) {
    int length = octets.length();
    final char[] chars   = octets.toCharArray();
    char[] swapped = new char[length];
    
    if (((double) length / 2) != (length / 2)) {
      swapped[length - 1] = chars[length - 1];
      length--;
    }
    
    for(int i = 0; i < length; i = i + 2) {
      swapped[i]     = chars[i + 1];
      swapped[i + 1] = chars[i]; 
    }
    return new String(swapped);
  }
  
  /**
   * This will split an 8bit value into two integers
   * representing the upper and lower 4 bits respectively.
   * The upper bits will be shifted 4 bits rightwards.
   * <BR>Example: The integer value 0xC2 (11000010) will be
   * split into the two values [0xC,0x2] ([1100, 0010]).
   * 
   * @param value The 8bit integer value to split
   * @return An array of length 2 that contains the upper and lower bits
   */
  public static int[] split8bitInto4bit(final int value) {
    final int upperValue = (value & 0xF0) >> 4;
    final int lowerValue = (value & 0xF);
    return new int[] {upperValue, lowerValue};
  }
  
  /**
   * This will convert a String into an int array.
   * 
   * @param data The string to convert
   * @return An int array
   */
  public static int[] stringToInt(final String data) {
    final int[] ints;
    if (data == null) {
      ints = new int[0];
    } else {
      final char[] chars = data.toCharArray();
      final int length = chars.length;
      ints = new int[length];
      for (int i = 0; i < length; i++) {
        ints[i] = chars[i];
      }
    }
    return ints;
  }
}